{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "chapter-intro",
   "metadata": {},
   "source": [
    "# 第 5 章：AI 智能体的记忆系统\n",
    "\n",
    "> 本笔记文件需要与《LangGraph实战》的第 5 章的内容配套使用。\n",
    "\n",
    "在 AI 智能体不断演进的征程中，我们已然认识到，赋予它们类似人类的记忆能力是构建真正智能和实用系统的核心所在。本章将深入探讨 AI 智能体记忆系统的关键概念和实践方法，为你揭示如何利用 LangGraph 框架赋予你的智能体持久的知识和情境感知能力。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "environment-setup",
   "metadata": {},
   "source": [
    "### 🚀 环境准备\n",
    "\n",
    "首先加载必要的环境变量配置和基础模块："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "cell-0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "section-5-1",
   "metadata": {},
   "source": [
    "## 5.1 短期记忆 vs. 长期记忆：平衡语境与持久性\n",
    "\n",
    "要创建真正智能且有助力的 AI 智能体，就需要为其配备能够模仿人类认知细微之处的记忆系统。正如人类利用短期记忆和长期记忆来进行有效的互动和学习一样，AI 智能体也能从类似的二分法中获益。\n",
    "\n",
    "### 记忆系统的重要性\n",
    "\n",
    "- **短期记忆**：对话的即时性至关重要，使智能体能够在正在进行的对话中处理当前的用户输入，理解细微之处，并保持对话流程\n",
    "- **长期记忆**：提供连续性和成长性，使智能体能够超越个别互动，建立持久的身份，从累积的经验中学习\n",
    "\n",
    "一个健壮的 AI 智能体记忆架构需要一种区分对待的方法，策略性地同时使用短期记忆和长期记忆。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "section-5-1-1",
   "metadata": {},
   "source": [
    "### 5.1.1 短期记忆：维持对话的连贯性\n",
    "\n",
    "短期记忆是 AI 智能体进行连贯、回合式对话的基石。它使智能体能够在整个对话过程中保持语境理解。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "example-5-1",
   "metadata": {},
   "source": [
    "##### 示例 5-1：在 LangGraph 中通过对话历史记录管理短期记忆\n",
    "\n",
    "首先定义 AgentState 结构来存储对话历史记录："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "cell-1",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import TypedDict, List, Dict, Any\n",
    "from langchain_core.messages import BaseMessage, HumanMessage, AIMessage\n",
    "\n",
    "class AgentState(TypedDict):\n",
    "    messages: List[BaseMessage]  # 消息列表，作为对话历史记录\n",
    "    intermediate_results: Dict[str, Any]  # 短期记忆中的其他数据"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "explanation-5-1",
   "metadata": {},
   "source": [
    "**💡 短期记忆核心概念**：\n",
    "\n",
    "- `AgentState`：定义智能体的状态结构，包含消息列表和中间结果\n",
    "- `messages`：存储对话历史的核心字段，支持用户和AI消息\n",
    "- `intermediate_results`：存储会话期间的临时数据，如任务状态、中间计算结果等"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "example-5-2",
   "metadata": {},
   "source": [
    "##### 示例 5-2：在 LangGraph 中通过中间结果管理短期记忆\n",
    "\n",
    "实现添加消息到对话历史的函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "cell-2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "添加用户消息后的状态： 1 条消息\n",
      "添加AI消息后的状态： 2 条消息\n"
     ]
    }
   ],
   "source": [
    "def add_user_message(state: AgentState, user_message: str) -> Dict[str, List[BaseMessage]]:\n",
    "    \"\"\"向对话历史记录添加用户消息\"\"\"\n",
    "    new_message = HumanMessage(content=user_message)\n",
    "    return {\"messages\": state[\"messages\"] + [new_message]}\n",
    "\n",
    "def add_ai_message(state: AgentState, ai_response: str) -> Dict[str, List[BaseMessage]]:\n",
    "    \"\"\"向对话历史记录添加 AI 响应\"\"\"\n",
    "    new_message = AIMessage(content=ai_response)\n",
    "    return {\"messages\": state[\"messages\"] + [new_message]}\n",
    "\n",
    "# 测试短期记忆管理\n",
    "initial_state = AgentState(messages=[], intermediate_results={})\n",
    "state_with_user_msg = add_user_message(initial_state, \"你好，我是用户\")\n",
    "print(\"添加用户消息后的状态：\", len(state_with_user_msg[\"messages\"]), \"条消息\")\n",
    "\n",
    "final_state = add_ai_message(state_with_user_msg, \"你好！我是AI助手，很高兴为您服务\")\n",
    "print(\"添加AI消息后的状态：\", len(final_state[\"messages\"]), \"条消息\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "explanation-5-2",
   "metadata": {},
   "source": [
    "**💡 消息管理机制**：\n",
    "\n",
    "- **消息累积**：新消息通过列表连接的方式添加到历史记录中\n",
    "- **类型区分**：`HumanMessage`和`AIMessage`分别表示用户和AI的消息\n",
    "- **状态更新**：返回字典格式的状态更新，符合LangGraph的状态管理机制"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "example-5-3",
   "metadata": {},
   "source": [
    "##### 示例 5-3：在 LangGraph 中通过截断管理短期记忆\n",
    "\n",
    "随着对话的延长，需要管理内存和成本。这里演示两种截断策略："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cell-3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "原始消息数量: 6\n",
      "截断后消息数量: 3\n",
      "消息 1: AIMessage - 第二条AI回复\n",
      "消息 2: HumanMessage - 第三条用户消息\n",
      "消息 3: AIMessage - 第三条AI回复\n"
     ]
    }
   ],
   "source": [
    "# 注意：这里模拟 trim_messages 功能，实际使用时需要安装完整的 LangChain\n",
    "def truncate_history(state: AgentState, max_messages: int) -> Dict[str, List[BaseMessage]]:\n",
    "    \"\"\"截断对话历史记录，仅保留最近的消息\"\"\"\n",
    "    truncated_messages = state[\"messages\"][-max_messages:]\n",
    "    return {\"messages\": truncated_messages}\n",
    "\n",
    "def simulate_trim_message_history_by_token(state: AgentState, max_tokens: int) -> Dict[str, List[BaseMessage]]:\n",
    "    \"\"\"模拟根据 token 计数修剪消息历史记录\"\"\"\n",
    "    if not state[\"messages\"]:\n",
    "        return {\"messages\": []}\n",
    "    \n",
    "    # 简化的 token 计算（实际中会使用真实的 tokenizer）\n",
    "    messages = state[\"messages\"]\n",
    "    total_tokens = 0\n",
    "    kept_messages = []\n",
    "    \n",
    "    # 从后往前保留消息，直到达到 token 限制\n",
    "    for message in reversed(messages):\n",
    "        # 简化：假设每个字符约等于 0.5 个 token\n",
    "        message_tokens = len(message.content) * 0.5\n",
    "        if total_tokens + message_tokens <= max_tokens:\n",
    "            kept_messages.insert(0, message)\n",
    "            total_tokens += message_tokens\n",
    "        else:\n",
    "            break\n",
    "    \n",
    "    return {\"messages\": kept_messages}\n",
    "\n",
    "# 创建一个包含多条消息的测试状态\n",
    "test_state = AgentState(\n",
    "    messages=[\n",
    "        HumanMessage(content=\"第一条用户消息\"),\n",
    "        AIMessage(content=\"第一条AI回复\"),\n",
    "        HumanMessage(content=\"第二条用户消息\"),\n",
    "        AIMessage(content=\"第二条AI回复\"),\n",
    "        HumanMessage(content=\"第三条用户消息\"),\n",
    "        AIMessage(content=\"第三条AI回复\")\n",
    "    ],\n",
    "    intermediate_results={}\n",
    ")\n",
    "\n",
    "# 测试消息数量截断\n",
    "truncated_state = truncate_history(test_state, 3)\n",
    "print(f\"原始消息数量: {len(test_state['messages'])}\")\n",
    "print(f\"截断后消息数量: {len(truncated_state['messages'])}\")\n",
    "\n",
    "# 显示截断后的消息内容\n",
    "for i, msg in enumerate(truncated_state['messages']):\n",
    "    print(f\"消息 {i+1}: {type(msg).__name__} - {msg.content}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "explanation-5-3",
   "metadata": {},
   "source": [
    "**💡 记忆优化策略**：\n",
    "\n",
    "- **数量截断**：`truncate_history`保留最新的N条消息，简单有效\n",
    "- **Token截断**：考虑实际Token消耗，更精确地控制上下文长度\n",
    "- **策略选择**：根据模型上下文窗口限制和成本考虑选择合适的截断策略\n",
    "- **消息完整性**：确保截断后的对话历史仍然保持逻辑完整性"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "example-5-4",
   "metadata": {},
   "source": [
    "##### 示例 5-4：在 LangGraph 中通过摘要管理短期记忆\n",
    "\n",
    "更精细的策略是使用 LLM 总结对话历史："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "cell-4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "摘要前消息数量: 7\n",
      "摘要后消息数量: 2\n",
      "\n",
      "摘要后的消息内容：\n",
      "1. AIMessage: 对话摘要：讨论了6轮交互，涉及用户查询和AI回复。\n",
      "2. HumanMessage: 现在我想了解面向对象编程\n"
     ]
    }
   ],
   "source": [
    "# 模拟摘要功能\n",
    "def simulate_summarize_history(state: AgentState) -> Dict[str, List[BaseMessage]]:\n",
    "    \"\"\"模拟使用 LLM 总结对话历史记录\"\"\"\n",
    "    messages = state[\"messages\"]\n",
    "    if len(messages) <= 2:  # 如果消息太少，不需要摘要\n",
    "        return {\"messages\": messages}\n",
    "    \n",
    "    # 模拟摘要生成（实际中会调用 LLM）\n",
    "    conversation_content = []\n",
    "    for m in messages[:-1]:  # 排除最后一条消息\n",
    "        role = \"用户\" if isinstance(m, HumanMessage) else \"AI助手\"\n",
    "        conversation_content.append(f\"{role}: {m.content}\")\n",
    "    \n",
    "    # 简化的摘要生成\n",
    "    summary = f\"对话摘要：讨论了{len(messages)-1}轮交互，涉及用户查询和AI回复。\"\n",
    "    \n",
    "    # 将历史记录替换为摘要和最新的用户消息\n",
    "    summary_message = AIMessage(content=summary)\n",
    "    last_message = messages[-1]  # 保留最后一条消息\n",
    "    new_messages = [summary_message, last_message]\n",
    "    \n",
    "    return {\"messages\": new_messages}\n",
    "\n",
    "# 测试摘要功能\n",
    "rich_conversation_state = AgentState(\n",
    "    messages=[\n",
    "        HumanMessage(content=\"我想学习Python编程\"),\n",
    "        AIMessage(content=\"很好！Python是一门优秀的编程语言。我建议从基础语法开始学习。\"),\n",
    "        HumanMessage(content=\"我应该先学什么？\"),\n",
    "        AIMessage(content=\"建议先学习变量、数据类型、条件语句和循环。\"),\n",
    "        HumanMessage(content=\"能给我推荐一些学习资源吗？\"),\n",
    "        AIMessage(content=\"推荐《Python编程：从入门到实践》这本书，还有官方文档和在线教程。\"),\n",
    "        HumanMessage(content=\"现在我想了解面向对象编程\")\n",
    "    ],\n",
    "    intermediate_results={}\n",
    ")\n",
    "\n",
    "print(f\"摘要前消息数量: {len(rich_conversation_state['messages'])}\")\n",
    "\n",
    "# 使用摘要功能\n",
    "summarized_state = simulate_summarize_history(rich_conversation_state)\n",
    "print(f\"摘要后消息数量: {len(summarized_state['messages'])}\")\n",
    "\n",
    "print(\"\\n摘要后的消息内容：\")\n",
    "for i, msg in enumerate(summarized_state['messages']):\n",
    "    print(f\"{i+1}. {type(msg).__name__}: {msg.content}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "explanation-5-4",
   "metadata": {},
   "source": [
    "**💡 智能摘要机制**：\n",
    "\n",
    "- **语义保留**：LLM摘要能够保留对话的核心语义信息，而不仅仅是截断\n",
    "- **上下文压缩**：将长对话压缩为简洁的摘要，大幅减少Token使用\n",
    "- **错误处理**：包含异常处理机制，确保摘要失败时有备用方案\n",
    "- **最新信息保留**：总是保留最新的用户消息，确保当前上下文不丢失"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "section-5-2",
   "metadata": {},
   "source": [
    "## 5.2 深入探索记忆存储：设置和管理长期知识\n",
    "\n",
    "在明确了长期记忆的重要作用后，我们现在将注意力转向在 LangGraph 中实现此类记忆的实用机制。本节将深入探讨 LangGraph 记忆存储，这是一个多功能的抽象，旨在管理智能体的持久化、跨线程知识。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "section-5-2-1",
   "metadata": {},
   "source": [
    "### 5.2.1 核心记忆存储操作：设置和基本用法\n",
    "\n",
    "LangGraph 记忆存储以 `BaseStore` 类及其实现为代表，为 LangGraph 应用程序中的长期记忆管理提供了基本抽象。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "example-5-5",
   "metadata": {},
   "source": [
    "##### 示例 5-5 & 5-6：使用 InMemoryStore 实例化记忆存储"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cell-5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "记忆已保存，键为：8ee2fce6-145b-42b3-953d-40dea32e6f26，命名空间为：('example_user', 'user_info')\n"
     ]
    }
   ],
   "source": [
    "import uuid\n",
    "from langgraph.store.memory import InMemoryStore\n",
    "\n",
    "in_memory_store = InMemoryStore()\n",
    "\n",
    "# 定义用户特定数据的命名空间\n",
    "user_id = \"example_user\"\n",
    "namespace_for_user_data = (user_id, \"user_info\")\n",
    "\n",
    "# 为记忆条目生成唯一键\n",
    "memory_key = str(uuid.uuid4())\n",
    "\n",
    "# 创建一个字典来保存用户姓名作为记忆值\n",
    "memory_value = {\"user_name\": \"Example User\"}\n",
    "\n",
    "# 使用 put 将记忆存储在 InMemoryStore 中\n",
    "in_memory_store.put(namespace_for_user_data, memory_key, memory_value)\n",
    "\n",
    "print(f\"记忆已保存，键为：{memory_key}，命名空间为：{namespace_for_user_data}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "explanation-5-5",
   "metadata": {},
   "source": [
    "**💡 记忆存储架构**：\n",
    "\n",
    "- **命名空间设计**：使用元组作为逻辑容器，支持层次化组织\n",
    "- **键值存储**：键作为唯一标识符，值为字典格式提供灵活性\n",
    "- **时间戳管理**：自动添加创建和更新时间戳\n",
    "- **可扩展性**：可以轻松扩展以支持更复杂的查询和索引功能"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "example-5-6",
   "metadata": {},
   "source": [
    "##### 示例 5-7：使用 search 和 get 检索记忆"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "cell-6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "命名空间中的所有用户记忆：\n",
      "{'namespace': ['example_user', 'user_info'], 'key': '8ee2fce6-145b-42b3-953d-40dea32e6f26', 'value': {'user_name': 'Example User'}, 'created_at': '2025-08-27T12:15:04.250800+00:00', 'updated_at': '2025-08-27T12:15:04.250815+00:00', 'score': None}\n",
      "\n",
      "使用键 '8ee2fce6-145b-42b3-953d-40dea32e6f26' 检索到的记忆：\n",
      "{'namespace': ['example_user', 'user_info'], 'key': '8ee2fce6-145b-42b3-953d-40dea32e6f26', 'value': {'user_name': 'Example User'}, 'created_at': '2025-08-27T12:15:04.250800+00:00', 'updated_at': '2025-08-27T12:15:04.250815+00:00'}\n"
     ]
    }
   ],
   "source": [
    "# 检索 user_info 命名空间中的所有记忆（没有查询或过滤器）\n",
    "all_user_memories = in_memory_store.search(namespace_for_user_data)\n",
    "print(\"命名空间中的所有用户记忆：\")\n",
    "for record in all_user_memories:\n",
    "    print(record.dict()) # 打印 MemoryRecord 字典表示\n",
    "\n",
    "# 使用 'get' 通过键检索记忆\n",
    "retrieved_memory_record = in_memory_store.get(namespace_for_user_data, memory_key)\n",
    "print(f\"\\n使用键 '{memory_key}' 检索到的记忆：\")\n",
    "print(retrieved_memory_record.dict()) # 打印 MemoryRecord 字典表示"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f552606",
   "metadata": {},
   "source": [
    "### 5.2.2 通过语义搜索增强记忆检索\n",
    "\n",
    "LangGraph 记忆存储的语义搜索功能显著提升了记忆检索的有效性，使智能体能够基于语义相似性而非简单的关键词匹配来检索信息。这种机制通过向量嵌入技术实现，能够捕捉文本的深层语义特征，从而获得更符合语境的检索结果。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "369a204c",
   "metadata": {},
   "source": [
    "##### 示例 5-8：使用 BGE-M3 向量化模型配置 InMemoryStore 以进行语义搜索"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "494403f2",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import OpenAIEmbeddings\n",
    "from langgraph.store.memory import InMemoryStore\n",
    "\n",
    "# 初始化 BGE-M3 向量化模型\n",
    "embeddings = OpenAIEmbeddings(model=\"BAAI/bge-m3\") # 或其他向量化模型\n",
    "\n",
    "# 使用语义搜索索引配置 InMemoryStore\n",
    "store_with_semantic_search = InMemoryStore(\n",
    "    index={\n",
    "        \"embed\": embeddings.embed_documents, \n",
    "        \"dims\": 1024, # BGE-M3 的向量维度\n",
    "        \"fields\": [\"memory_content\"] # 仅向量化 \"memory_content\" 字段（可选）\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74f4395e",
   "metadata": {},
   "source": [
    "##### 示例 5-9：使用 put 执行记忆保存"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "f37f7250",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 保存将为语义搜索索引的记忆（默认行为）\n",
    "store_with_semantic_search.put(\n",
    "    (\"user_789\", \"food_memories\"),\n",
    "    \"memory_1\",\n",
    "    {\"memory_content\": \"我真的很喜欢辛辣的印度咖喱。\"},\n",
    ")\n",
    "\n",
    "# 保存另一个记忆，显式禁用此条目的索引\n",
    "store_with_semantic_search.put(\n",
    "    (\"user_789\", \"system_metadata\"),\n",
    "    \"memory_2\",\n",
    "    {\"memory_content\": \"用户入职已完成。\", \"status\": \"completed\"},\n",
    "    index=False, # 禁用此记忆的索引\n",
    ")\n",
    "\n",
    "# 保存一个记忆，覆盖默认索引字段并仅索引 \"context\"\n",
    "store_with_semantic_search.put(\n",
    "    (\"user_789\", \"restaurant_reviews\"),\n",
    "    \"memory_3\",\n",
    "    {\"memory_content\": \"服务很慢，但食物很好。\", \"context\": \"对 'The Italian Place' 餐厅的评论\"},\n",
    "    index=[\"context\"] # 仅索引 \"context\" 字段\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef141284",
   "metadata": {},
   "source": [
    "##### 示例 5-10：使用 search 执行语义记忆检索"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "22453f4d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "查询的语义搜索结果： 该用户喜欢哪种食物？\n",
      "记忆键：memory_1，相似度评分：0.39143762261467724\n",
      "记忆内容：{'memory_content': '我真的很喜欢辛辣的印度咖喱。'}\n",
      "==============================\n"
     ]
    }
   ],
   "source": [
    "# 语义搜索食物偏好\n",
    "search_query = \"该用户喜欢哪种食物？\"\n",
    "semantic_memory_results = store_with_semantic_search.search(\n",
    "    (\"user_789\", \"food_memories\"), query=search_query, limit=2\n",
    ")\n",
    "\n",
    "print(\"查询的语义搜索结果：\", search_query)\n",
    "for record in semantic_memory_results:\n",
    "    print(f\"记忆键：{record.key}，相似度评分：{record.score}\")\n",
    "    print(f\"记忆内容：{record.value}\")\n",
    "    print(\"=\" * 30)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4716c83f",
   "metadata": {},
   "source": [
    "## 5.3 记忆系统的实际应用\n",
    "\n",
    "在系统阐述短期记忆与长期记忆的核心概念，并深入解析 LangGraph 记忆存储机制后，我们将聚焦实际应用场景。本节重点探讨记忆系统如何显著增强 AI 智能体的功能表现，针对每个典型用例，我们将详细说明记忆应用价值分析、概念性代码示例，以及实际集成实施方案。其中，我们会特别关注记忆数据更新时机选择、信息提取技术，以及如何通过 TrustCall 机制提升记忆数据提取的可靠性。\n",
    "\n",
    "### 5.3.1 个性化推荐\n",
    "\n",
    "长期记忆最具价值的应用场景之一是实现个性化服务。通过持续记录用户偏好、兴趣点及历史交互数据，AI 智能体能够提供精准定制的推荐内容，从而显著提升用户参与度与满意度。以下示例主要依托语义记忆实现用户画像和偏好的跨会话持久化存储。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec067600",
   "metadata": {},
   "source": [
    "##### 示例 5-12：个性化推荐的示例用法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "c4c1e130",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "初始推荐:\n",
      "\n",
      "\n",
      "根据张三对**电子产品**和**书籍**的偏好，以下是为您量身推荐的产品列表：\n",
      "\n",
      "---\n",
      "\n",
      "### 📱 **电子产品推荐**  \n",
      "1. **Apple iPhone 15 Pro**  \n",
      "   - **特点**: 高性能A17芯片、Pro级相机系统、灵动岛设计、钛金属机身  \n",
      "   - **适用场景**: 适合追求科技前沿、拍照和性能的用户  \n",
      "   - **价格**: ¥7999起  \n",
      "\n",
      "2. **Sony WH-1000XM5 无线降噪耳机**  \n",
      "   - **特点**: 顶级降噪效果、30小时续航、高清音质、轻巧设计  \n",
      "   - **适用场景**: 旅途、办公、居家使用，需要沉浸式音效  \n",
      "   - **价格**: ¥1999  \n",
      "\n",
      "3. **Amazon Echo Dot (5th Gen)**  \n",
      "   - **特点**: 智能语音助手、音乐播放、智能家居控制、性价比高  \n",
      "   - **适用场景**: 智能家居爱好者、需要语音控制家电的用户  \n",
      "   - **价格**: ¥299  \n",
      "\n",
      "4. **DJI Osmo Pocket 3**  \n",
      "   - **特点**: 轻巧便携、专业运动相机功能、AI自动追踪、4K视频  \n",
      "   - **适用场景**: 影视爱好者、旅行记录或Vlog拍摄  \n",
      "   - **价格**: ¥2499  \n",
      "\n",
      "5. **小米扫地机器人 Pro 2**  \n",
      "   - **特点**: 超薄设计、LDS激光导航、自动集尘、兼容多品牌吸尘器  \n",
      "   - **适用场景**: 希望解放双手、保持家居清洁的用户  \n",
      "   - **价格**: ¥1499  \n",
      "\n",
      "---\n",
      "\n",
      "### 📚 **书籍推荐**  \n",
      "1. **《时间的秩序》——卡洛·罗韦利**  \n",
      "   - **类型**: 科普、物理学  \n",
      "   - **亮点**: 用通俗语言解释复杂物理概念，适合对宇宙和时间感兴趣的读者  \n",
      "   - **价格**: ¥45  \n",
      "\n",
      "2. **《深度工作》——卡尔·纽波特**  \n",
      "   - **类型**: 自我提升、效率管理  \n",
      "   - **亮点**: 提供专注力训练和深度工作的实用策略，适合职场人士  \n",
      "   - **价格**: ¥39  \n",
      "\n",
      "3. **《三体》系列（刘慈欣）**  \n",
      "   - **类型**: 科幻、文学  \n",
      "   - **亮点**: 中国科幻巅峰之作，探讨文明与宇宙的宏大命题  \n",
      "   - **价格**: ¥60（套装）  \n",
      "\n",
      "4. **《人类简史》——尤瓦尔·赫拉利**  \n",
      "   - **类型**: 历史、社会学  \n",
      "   - **亮点**: 从认知革命到农业革命，梳理人类文明发展的关键节点  \n",
      "   - **价格**: ¥55  \n",
      "\n",
      "5. **《Python编程：从入门到实践》——Eric Matthes**  \n",
      "   - **类型**: 编程、技术  \n",
      "   - **亮点**: 适合初学者，涵盖Python基础与实际项目开发  \n",
      "   - **价格**: ¥79  \n",
      "\n",
      "---\n",
      "\n",
      "### 🎁 **组合推荐**  \n",
      "- **科技爱好者套装**: iPhone 15 Pro + 《时间的秩序》 + 《Python编程：从入门到实践》  \n",
      "- **高效办公组合**: Amazon Echo Dot + 《深度工作》 + 扫地机器人 Pro 2  \n",
      "\n",
      "---\n",
      "\n",
      "需要更具体的推荐（如价格区间、品牌偏好或书籍类型）吗？可以随时告诉我！ 😊\n",
      "==================================================\n",
      "更新后的用户资料:\n",
      "{\n",
      "  \"preferred_name\": \"张三\",\n",
      "  \"preferred_product_categories\": [\n",
      "    \"书籍\",\n",
      "    \"运动鞋\",\n",
      "    \"电子产品\",\n",
      "    \"户外装备\"\n",
      "  ]\n",
      "}\n",
      "==================================================\n",
      "基于更新后资料的推荐:\n",
      "\n",
      "\n",
      "根据您的偏好（书籍、运动鞋、电子产品、户外装备），我为您推荐以下个性化产品：\n",
      "\n",
      "---\n",
      "\n",
      "### **1. 书籍推荐**\n",
      "- **推荐方向**：  \n",
      "  - 热门小说（如《三体》《百年孤独》）  \n",
      "  - 自我提升类（如《精进》《原子习惯》）  \n",
      "  - 户外生存与探险类（如《荒野求生》《徒步旅行指南》）  \n",
      "  - 科技与创新类（如《人工智能简史》《未来简史》）  \n",
      "\n",
      "---\n",
      "\n",
      "### **2. 运动鞋推荐**\n",
      "- **推荐方向**：  \n",
      "  - 跑步鞋（如New Balance 990系列、Nike Air Zoom Pegasus）  \n",
      "  - 健身鞋（如Lululemon CrossFit系列、Under Armour UA Build 2）  \n",
      "  - 户外徒步鞋（如Salomon X Ultra系列、Merrell Moab系列）  \n",
      "  - 舒适日常鞋（如Veja V-16、Adidas Ultraboost）  \n",
      "\n",
      "---\n",
      "\n",
      "### **3. 电子产品推荐**\n",
      "- **推荐方向**：  \n",
      "  - 高性能手机（如iPhone 14 Pro、三星Galaxy S23 Ultra）  \n",
      "  - 降噪耳机（如Sony WH-1000XM5、Bose QuietComfort 35）  \n",
      "  - 智能手表（如Apple Watch Series 8、Garmin Venu 2）  \n",
      "  - 户外专用设备（如Garmin GPS手表、GoPro Hero 11相机）  \n",
      "\n",
      "---\n",
      "\n",
      "### **4. 户外装备推荐**\n",
      "- **推荐方向**：  \n",
      "  - 帐篷（如Nemo Dagger 2、Black Diamond Ultralight）  \n",
      "  - 登山杖（如Black Diamond Reach 5、Petzl Cradle）  \n",
      "  - 背包（如Osprey Atmos AG 65、Deuter Aircontact Lite）  \n",
      "  - 防水服（如Patagonia Torrentshell、The North Face Ridge 3.0）  \n",
      "\n",
      "---\n",
      "\n",
      "### **5. 跨类别组合推荐**\n",
      "- **运动+户外套装**：  \n",
      "  - 跑步鞋 + 防水背包 + 户外耳机（适合徒步旅行或户外运动）  \n",
      "- **电子产品+书籍**：  \n",
      "  - 电子书阅读器（如Kindle Paperwhite） + 科技类书籍（如《黑客与画家》）  \n",
      "\n",
      "---\n",
      "\n",
      "如果您有具体的预算、品牌偏好或使用场景（如登山、跑步、露营等），可以告诉我，我会进一步优化推荐！ 📘👟📱🥾\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "\n",
    "from langchain_core.messages import AIMessage, HumanMessage\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnableConfig\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "from langgraph.graph import END, START, StateGraph, MessagesState\n",
    "from langgraph.store.memory import BaseStore, InMemoryStore\n",
    "\n",
    "# 假设 'fetch_product_recommendations'、'format_recommendation_message'、'UserProfile' 已在其他地方定义\n",
    "\n",
    "recommendation_prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"你是一个乐于助人的推荐引擎。根据用户资料，提供个性化的产品推荐。\"),\n",
    "    (\"human\", \"{user_profile_summary}\")\n",
    "])\n",
    "recommendation_chain = recommendation_prompt | ChatOpenAI(model=\"Qwen/Qwen3-8B\") |  (lambda x: {\"messages\": [AIMessage(content=x.content)]})\n",
    "\n",
    "def recommend_products(state: MessagesState, config: RunnableConfig, store: BaseStore):\n",
    "    \"\"\"根据用户存储的偏好向用户推荐产品。\"\"\"\n",
    "    user_id = config[\"configurable\"][\"user_id\"]\n",
    "    namespace = (\"user_profiles\", user_id)\n",
    "    user_profile_record = store.get(namespace, \"profile\")\n",
    "    user_profile = user_profile_record.value if user_profile_record else {}\n",
    "\n",
    "    user_profile_summary = format_user_profile_summary(user_profile) # 用于格式化提示的用户资料字典的函数\n",
    "\n",
    "    # 调用推荐链\n",
    "    result = recommendation_chain.invoke({\"user_profile_summary\": user_profile_summary})\n",
    "    return result\n",
    "\n",
    "def format_user_profile_summary(user_profile: dict) -> str:\n",
    "    \"\"\"将用户资料字典格式化为字符串以进行提示注入。\"\"\"\n",
    "    name = user_profile.get(\"preferred_name\", \"用户\")\n",
    "    categories = \", \".join(user_profile.get(\"preferred_product_categories\", [\"产品\"]))\n",
    "    return f\"用户名为 {name}。他们偏好的产品类别是：{categories}。\"\n",
    "\n",
    "\n",
    "def extract_preference_updates(state: MessagesState) -> dict:\n",
    "    \"\"\"从最新的用户消息中提取用户偏好更新。\"\"\"\n",
    "    latest_message_content = state[\"messages\"][-2].content\n",
    "    # 示例：使用 LLM 提取偏好 - 替换为实际的提取逻辑\n",
    "    extraction_prompt = ChatPromptTemplate.from_messages([\n",
    "        (\"system\", \"从用户消息中提取用户的产品类别偏好。以 JSON 字典形式返回，外层不要包裹 ```json```， 键为 'preferred_product_categories'，值为类别列表。如果没有表达偏好，则返回一个空字典。\"),\n",
    "        (\"human\", \"{user_message}\")\n",
    "    ])\n",
    "    extraction_chain = extraction_prompt | ChatOpenAI(model=\"Qwen/Qwen2.5-7B-Instruct\") # 如果需要结构化输出，请替换为合适的链\n",
    "\n",
    "    preferences_json = extraction_chain.invoke({\"user_message\": latest_message_content})\n",
    "    try:\n",
    "        preferences = json.loads(preferences_json.content) # 假设 LLM 返回 JSON 字符串\n",
    "        return preferences\n",
    "    except json.JSONDecodeError:\n",
    "        return {} # 如果提取失败，则返回空字典\n",
    "\n",
    "def update_user_profile_node(state: MessagesState, config: RunnableConfig, store: BaseStore):\n",
    "    \"\"\"在\"热路径\"中触发的记忆存储中更新用户资料。\"\"\"\n",
    "    user_id = config[\"configurable\"][\"user_id\"]\n",
    "    namespace = (\"user_profiles\", user_id)\n",
    "    user_profile_record = store.get(namespace, \"profile\")\n",
    "    user_profile = user_profile_record.value if user_profile_record else {}\n",
    "\n",
    "    preference_updates = extract_preference_updates(state) # 从当前轮次提取偏好\n",
    "    \n",
    "    updated_profile = user_profile.copy() # 创建副本以避免修改原始字典\n",
    "    if \"preferred_product_categories\" in preference_updates: # 合并或更新偏好\n",
    "        updated_profile[\"preferred_product_categories\"] = list(set(updated_profile.get(\"preferred_product_categories\", []) + preference_updates[\"preferred_product_categories\"])) # 示例：合并列表\n",
    "    \n",
    "    store.put(namespace, \"profile\", updated_profile) # 保存更新后的资料\n",
    "\n",
    "    return {} # 节点应返回字典\n",
    "\n",
    "\n",
    "# LangGraph 中的示例用法\n",
    "memory_store = InMemoryStore()\n",
    "\n",
    "builder = StateGraph(MessagesState)\n",
    "builder.add_node(\"recommend_products\", recommend_products)\n",
    "builder.add_node(\"update_profile\", update_user_profile_node) # 在\"热路径\"中更新资料的节点\n",
    "builder.add_edge(START, \"recommend_products\")\n",
    "builder.add_edge(\"recommend_products\", \"update_profile\") # 在推荐后更新资料\n",
    "builder.add_edge(\"update_profile\", END)\n",
    "\n",
    "graph = builder.compile(store=memory_store)\n",
    "\n",
    "# 初始化用户资料\n",
    "user_id = \"user_123\"\n",
    "memory_store.put(\n",
    "    (\"user_profiles\", user_id),\n",
    "    \"profile\",\n",
    "    {\n",
    "        \"preferred_name\": \"张三\",\n",
    "        \"preferred_product_categories\": [\"电子产品\", \"书籍\"]\n",
    "    }\n",
    ")\n",
    "\n",
    "# 执行图 - 第一次交互（获取推荐）\n",
    "config = {\"configurable\": {\"user_id\": user_id}}\n",
    "result = graph.invoke({\"messages\": [HumanMessage(content=\"你好\")]}, config=config)\n",
    "print(\"初始推荐:\")\n",
    "print(result[\"messages\"][-1].content)\n",
    "print(\"=\" * 50)\n",
    "\n",
    "# 模拟用户表达新的偏好\n",
    "user_message = \"我最近对户外装备和运动鞋很感兴趣。\"\n",
    "result = graph.invoke(\n",
    "    {\"messages\": result[\"messages\"] + [HumanMessage(content=user_message)]},\n",
    "    config=config\n",
    ")\n",
    "\n",
    "# 检查更新后的用户资料\n",
    "updated_profile = memory_store.get((\"user_profiles\", user_id), \"profile\").value\n",
    "print(\"更新后的用户资料:\")\n",
    "print(json.dumps(updated_profile, ensure_ascii=False, indent=2))\n",
    "print(\"=\" * 50)\n",
    "\n",
    "# 再次获取推荐，应该包含新的偏好\n",
    "result = graph.invoke({\"messages\": [HumanMessage(content=\"我又来了\")]}, config=config)\n",
    "print(\"基于更新后资料的推荐:\")\n",
    "print(result[\"messages\"][-1].content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "706abf14",
   "metadata": {},
   "source": [
    "### 5.3.2 多步骤的情境化任务\n",
    "\n",
    "对于需要引导用户完成多步骤任务或复杂工作流的 AI 智能体来说，记忆至关重要。短期记忆在跟踪任务的当前阶段、记住先前步骤中的用户输入，以及在整个过程中保持语境方面起着至关重要的作用。长期记忆可用于存储模板或成功的任务完成示例，以指导 AI 智能体的行为。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb3a9403",
   "metadata": {},
   "source": [
    "##### 示例 5-13：情境化任务完成的示例用法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d0185c8d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== 情境化任务完成系统演示 ===\n",
      "\n",
      "--- 航班预订演示 ---\n",
      "\n",
      "👤 用户: 我想预订一张机票\n",
      "🔄 处理任务 - 当前状态: step=0, type=None\n",
      "📋 检测到任务类型: flight_booking\n",
      "🤖 助手: 我来帮您预订航班。请告诉我您的出发城市：\n",
      "📊 当前步骤: 0\n",
      "🏁 任务完成: False\n",
      "\n",
      "👤 用户: 我从北京出发\n",
      "🔄 处理任务 - 当前状态: step=0, type=flight_booking\n",
      "🎯 处理用户输入: 我从北京出发 -> 提取字段: departure_city\n",
      "✅ 成功提取 departure_city: 北京\n",
      "🤖 助手: 已记录您的departure_city：北京。请告诉我您的目的地城市：\n",
      "📊 当前步骤: 1\n",
      "🏁 任务完成: False\n",
      "📋 已收集数据: {'departure_city': '北京'}\n",
      "\n",
      "👤 用户: 我要去上海\n",
      "🔄 处理任务 - 当前状态: step=1, type=flight_booking\n",
      "🎯 处理用户输入: 我要去上海 -> 提取字段: arrival_city\n",
      "✅ 成功提取 arrival_city: 上海\n",
      "🤖 助手: 已记录您的arrival_city：上海。请提供出发日期（格式：YYYY-MM-DD）：\n",
      "📊 当前步骤: 2\n",
      "🏁 任务完成: False\n",
      "📋 已收集数据: {'departure_city': '北京', 'arrival_city': '上海'}\n",
      "\n",
      "👤 用户: 2024-03-15\n",
      "🔄 处理任务 - 当前状态: step=2, type=flight_booking\n",
      "🎯 处理用户输入: 2024-03-15 -> 提取字段: departure_date\n",
      "✅ 成功提取 departure_date: 2024-03-15\n",
      "🤖 助手: 已记录您的departure_date：2024-03-15。请告诉我乘客人数：\n",
      "📊 当前步骤: 3\n",
      "🏁 任务完成: False\n",
      "📋 已收集数据: {'departure_city': '北京', 'arrival_city': '上海', 'departure_date': '2024-03-15'}\n",
      "\n",
      "👤 用户: 1人\n",
      "🔄 处理任务 - 当前状态: step=3, type=flight_booking\n",
      "🎯 处理用户输入: 1人 -> 提取字段: passengers\n",
      "✅ 成功提取 passengers: 1\n",
      "💾 保存任务记录到记忆...\n",
      "✅ 任务记录已保存: de22b6ec-2859-4fbd-ab61-c4d1c3898f40\n",
      "🤖 助手: ✨ 任务已完成并保存到您的记录中！如需查看历史记录或开始新任务，请随时告诉我。\n",
      "📊 当前步骤: 4\n",
      "🏁 任务完成: True\n",
      "📋 已收集数据: {'departure_city': '北京', 'arrival_city': '上海', 'departure_date': '2024-03-15', 'passengers': '1'}\n",
      "\n",
      "--- 检查保存的任务记忆 ---\n",
      "📋 用户完成的任务数量: 1\n",
      "✅ flight_booking: {'departure_city': '北京', 'arrival_city': '上海', 'departure_date': '2024-03-15', 'passengers': '1'}\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "from typing import Dict, Any, List\n",
    "from langgraph.graph import StateGraph, END, START\n",
    "from langgraph.store.memory import InMemoryStore, BaseStore\n",
    "from langchain_core.messages import BaseMessage, HumanMessage, AIMessage\n",
    "from langchain_core.runnables import RunnableConfig\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# 定义任务状态类型\n",
    "class TaskState(dict):\n",
    "    \"\"\"任务状态管理\"\"\"\n",
    "    def __init__(self, **kwargs):\n",
    "        super().__init__(kwargs)\n",
    "        if 'messages' not in self:\n",
    "            self['messages'] = []\n",
    "        if 'task_data' not in self:\n",
    "            self['task_data'] = {}\n",
    "        if 'current_step' not in self:\n",
    "            self['current_step'] = 0\n",
    "        if 'task_type' not in self:\n",
    "            self['task_type'] = None\n",
    "        if 'task_complete' not in self:\n",
    "            self['task_complete'] = False\n",
    "\n",
    "class TaskCompletionAgent:\n",
    "    \"\"\"多步骤任务完成智能体\"\"\"\n",
    "    \n",
    "    def __init__(self):\n",
    "        # 定义不同类型的任务模板\n",
    "        self.task_templates = {\n",
    "            \"flight_booking\": {\n",
    "                \"steps\": [\n",
    "                    {\"field\": \"departure_city\", \"prompt\": \"请告诉我您的出发城市：\", \"required\": True},\n",
    "                    {\"field\": \"arrival_city\", \"prompt\": \"请告诉我您的目的地城市：\", \"required\": True},\n",
    "                    {\"field\": \"departure_date\", \"prompt\": \"请提供出发日期（格式：YYYY-MM-DD）：\", \"required\": True},\n",
    "                    {\"field\": \"passengers\", \"prompt\": \"请告诉我乘客人数：\", \"required\": True}\n",
    "                ],\n",
    "                \"completion_message\": \"✈️ 航班信息已收集完毕！正在为您查找最佳航班...\"\n",
    "            },\n",
    "            \"restaurant_reservation\": {\n",
    "                \"steps\": [\n",
    "                    {\"field\": \"cuisine_type\", \"prompt\": \"您想预订哪种类型的餐厅？（如：中餐、西餐、日料等）\", \"required\": True},\n",
    "                    {\"field\": \"date_time\", \"prompt\": \"请提供用餐日期和时间：\", \"required\": True},\n",
    "                    {\"field\": \"party_size\", \"prompt\": \"请告诉我用餐人数：\", \"required\": True}\n",
    "                ],\n",
    "                \"completion_message\": \"🍽️ 餐厅预订信息已收集完毕！正在为您查找合适的餐厅...\"\n",
    "            }\n",
    "        }\n",
    "\n",
    "def process_task(state: TaskState, config: RunnableConfig, store: BaseStore):\n",
    "    \"\"\"处理任务的主要逻辑\"\"\"\n",
    "    print(f\"🔄 处理任务 - 当前状态: step={state.get('current_step', 0)}, type={state.get('task_type')}\")\n",
    "    \n",
    "    agent = TaskCompletionAgent()\n",
    "    \n",
    "    # 如果还没有任务类型，先检测任务类型\n",
    "    if not state.get('task_type'):\n",
    "        if not state['messages']:\n",
    "            return state\n",
    "        \n",
    "        last_message = state['messages'][-1].content.lower()\n",
    "        \n",
    "        # 检测任务类型\n",
    "        if any(keyword in last_message for keyword in ['机票', '航班', '飞机', '预订机票']):\n",
    "            task_type = \"flight_booking\"\n",
    "        elif any(keyword in last_message for keyword in ['餐厅', '预订', '吃饭', '订餐']):\n",
    "            task_type = \"restaurant_reservation\"\n",
    "        else:\n",
    "            task_type = \"flight_booking\"\n",
    "        \n",
    "        print(f\"📋 检测到任务类型: {task_type}\")\n",
    "        \n",
    "        # 初始化任务状态\n",
    "        state['task_type'] = task_type\n",
    "        state['current_step'] = 0\n",
    "        state['task_data'] = {}\n",
    "        state['task_complete'] = False\n",
    "        \n",
    "        # 添加确认消息和第一个问题\n",
    "        template = agent.task_templates[task_type]\n",
    "        first_step = template['steps'][0]\n",
    "        \n",
    "        confirmation_messages = {\n",
    "            \"flight_booking\": f\"我来帮您预订航班。{first_step['prompt']}\",\n",
    "            \"restaurant_reservation\": f\"我来帮您预订餐厅。{first_step['prompt']}\"\n",
    "        }\n",
    "        \n",
    "        ai_message = AIMessage(content=confirmation_messages[task_type])\n",
    "        state['messages'].append(ai_message)\n",
    "        return state\n",
    "    \n",
    "    # 如果已经完成任务，直接返回\n",
    "    if state.get('task_complete'):\n",
    "        return state\n",
    "    \n",
    "    # 处理信息收集\n",
    "    task_type = state['task_type']\n",
    "    template = agent.task_templates[task_type]\n",
    "    steps = template['steps']\n",
    "    current_step = state['current_step']\n",
    "    \n",
    "    # 检查是否还有步骤需要完成\n",
    "    if current_step >= len(steps):\n",
    "        # 所有信息已收集完毕\n",
    "        state['task_complete'] = True\n",
    "        response = template['completion_message']\n",
    "        response += \"\\n\\n📋 收集到的信息：\\n\"\n",
    "        for key, value in state['task_data'].items():\n",
    "            response += f\"• {key}: {value}\\n\"\n",
    "        \n",
    "        ai_message = AIMessage(content=response)\n",
    "        state['messages'].append(ai_message)\n",
    "        return state\n",
    "    \n",
    "    # 检查是否有新的用户消息需要处理\n",
    "    if len(state['messages']) >= 2 and isinstance(state['messages'][-1], HumanMessage):\n",
    "        # 获取当前步骤信息\n",
    "        step_info = steps[current_step]\n",
    "        field_name = step_info['field']\n",
    "        user_message = state['messages'][-1].content\n",
    "        \n",
    "        print(f\"🎯 处理用户输入: {user_message} -> 提取字段: {field_name}\")\n",
    "        \n",
    "        # 尝试提取信息\n",
    "        extracted_value = extract_field_value(user_message, field_name)\n",
    "        \n",
    "        if extracted_value and extracted_value != \"NOT_FOUND\":\n",
    "            # 成功提取信息\n",
    "            state['task_data'][field_name] = extracted_value\n",
    "            state['current_step'] += 1\n",
    "            \n",
    "            print(f\"✅ 成功提取 {field_name}: {extracted_value}\")\n",
    "            \n",
    "            # 检查是否还有更多步骤\n",
    "            if state['current_step'] < len(steps):\n",
    "                next_step = steps[state['current_step']]\n",
    "                next_prompt = next_step['prompt']\n",
    "                response = f\"已记录您的{field_name}：{extracted_value}。{next_prompt}\"\n",
    "            else:\n",
    "                # 所有信息已收集完毕\n",
    "                state['task_complete'] = True\n",
    "                response = template['completion_message']\n",
    "                response += \"\\n\\n📋 收集到的信息：\\n\"\n",
    "                for key, value in state['task_data'].items():\n",
    "                    response += f\"• {key}: {value}\\n\"\n",
    "        else:\n",
    "            # 提取失败，重新询问\n",
    "            response = f\"抱歉，我没能理解您提供的{field_name}信息。{step_info['prompt']}\"\n",
    "        \n",
    "        # 添加AI响应\n",
    "        ai_message = AIMessage(content=response)\n",
    "        state['messages'].append(ai_message)\n",
    "    \n",
    "    return state\n",
    "\n",
    "def extract_field_value(user_message: str, field_name: str) -> str:\n",
    "    \"\"\"简化的信息提取函数\"\"\"\n",
    "    message_lower = user_message.lower()\n",
    "    \n",
    "    if field_name == \"departure_city\":\n",
    "        cities = [\"北京\", \"上海\", \"广州\", \"深圳\", \"杭州\", \"成都\", \"重庆\", \"西安\", \"南京\", \"武汉\"]\n",
    "        for city in cities:\n",
    "            if city in user_message:\n",
    "                return city\n",
    "    \n",
    "    elif field_name == \"arrival_city\":\n",
    "        cities = [\"北京\", \"上海\", \"广州\", \"深圳\", \"杭州\", \"成都\", \"重庆\", \"西安\", \"南京\", \"武汉\", \"大连\", \"青岛\"]\n",
    "        for city in cities:\n",
    "            if city in user_message:\n",
    "                return city\n",
    "    \n",
    "    elif field_name == \"departure_date\":\n",
    "        import re\n",
    "        date_patterns = [r'\\d{4}-\\d{2}-\\d{2}', r'\\d{1,2}月\\d{1,2}日', r'\\d{1,2}/\\d{1,2}']\n",
    "        for pattern in date_patterns:\n",
    "            match = re.search(pattern, user_message)\n",
    "            if match:\n",
    "                return match.group()\n",
    "    \n",
    "    elif field_name == \"passengers\":\n",
    "        import re\n",
    "        number_match = re.search(r'(\\d+)人?', user_message)\n",
    "        if number_match:\n",
    "            return number_match.group(1)\n",
    "    \n",
    "    elif field_name == \"cuisine_type\":\n",
    "        cuisine_types = [\"中餐\", \"西餐\", \"日料\", \"韩料\", \"意大利\", \"法餐\", \"泰餐\", \"印度\"]\n",
    "        for cuisine in cuisine_types:\n",
    "            if cuisine in user_message:\n",
    "                return cuisine\n",
    "    \n",
    "    elif field_name == \"party_size\":\n",
    "        import re\n",
    "        number_match = re.search(r'(\\d+)人?', user_message)\n",
    "        if number_match:\n",
    "            return number_match.group(1)\n",
    "    \n",
    "    elif field_name == \"date_time\":\n",
    "        if any(word in message_lower for word in [\"明天\", \"今天\", \"后天\"]):\n",
    "            return user_message\n",
    "        import re\n",
    "        if re.search(r'\\d+点|\\d+:\\d+', user_message):\n",
    "            return user_message\n",
    "    \n",
    "    return \"NOT_FOUND\"\n",
    "\n",
    "def save_task_memory(state: TaskState, config: RunnableConfig, store: BaseStore):\n",
    "    \"\"\"保存任务完成记录到长期记忆\"\"\"\n",
    "    print(\"💾 保存任务记录到记忆...\")\n",
    "    \n",
    "    if not state['task_data'] or not state['task_type']:\n",
    "        return state\n",
    "    \n",
    "    user_id = config.get(\"configurable\", {}).get(\"user_id\", \"default_user\")\n",
    "    \n",
    "    # 创建任务记录\n",
    "    task_record = {\n",
    "        \"task_type\": state['task_type'],\n",
    "        \"task_data\": state['task_data'],\n",
    "        \"completion_time\": \"2024-02-15T10:30:00Z\",\n",
    "        \"status\": \"completed\"\n",
    "    }\n",
    "    \n",
    "    # 保存到记忆存储\n",
    "    import uuid\n",
    "    record_key = str(uuid.uuid4())\n",
    "    namespace = (user_id, \"completed_tasks\")\n",
    "    \n",
    "    store.put(namespace, record_key, task_record)\n",
    "    print(f\"✅ 任务记录已保存: {record_key}\")\n",
    "    \n",
    "    # 添加确认消息\n",
    "    confirmation_message = AIMessage(content=\"✨ 任务已完成并保存到您的记录中！如需查看历史记录或开始新任务，请随时告诉我。\")\n",
    "    state['messages'].append(confirmation_message)\n",
    "    \n",
    "    return state\n",
    "\n",
    "# 创建简化的任务完成图\n",
    "def create_task_completion_graph():\n",
    "    \"\"\"创建任务完成流程图\"\"\"\n",
    "    store = InMemoryStore()\n",
    "    \n",
    "    # 创建状态图\n",
    "    workflow = StateGraph(dict)\n",
    "    \n",
    "    # 添加节点\n",
    "    workflow.add_node(\"process_task\", process_task)\n",
    "    workflow.add_node(\"save_memory\", save_task_memory)\n",
    "    \n",
    "    # 条件函数：检查是否需要保存记忆\n",
    "    def should_save_memory(state):\n",
    "        if state.get('task_complete', False) and state.get('task_data'):\n",
    "            return \"save_memory\"\n",
    "        else:\n",
    "            return \"end\"\n",
    "    \n",
    "    # 添加边\n",
    "    workflow.add_edge(START, \"process_task\")\n",
    "    workflow.add_conditional_edges(\n",
    "        \"process_task\",\n",
    "        should_save_memory,\n",
    "        {\n",
    "            \"save_memory\": \"save_memory\",\n",
    "            \"end\": END\n",
    "        }\n",
    "    )\n",
    "    workflow.add_edge(\"save_memory\", END)\n",
    "    \n",
    "    # 编译图\n",
    "    app = workflow.compile(store=store)\n",
    "    return app, store\n",
    "\n",
    "# 演示任务完成系统\n",
    "print(\"=== 情境化任务完成系统演示 ===\")\n",
    "\n",
    "# 创建应用和存储\n",
    "app, store = create_task_completion_graph()\n",
    "\n",
    "# 配置\n",
    "config = {\"configurable\": {\"user_id\": \"user_456\"}}\n",
    "\n",
    "print(\"\\n--- 航班预订演示 ---\")\n",
    "\n",
    "# 演示完整的多轮对话\n",
    "conversation_steps = [\n",
    "    \"我想预订一张机票\",\n",
    "    \"我从北京出发\", \n",
    "    \"我要去上海\",\n",
    "    \"2024-03-15\",\n",
    "    \"1人\"\n",
    "]\n",
    "\n",
    "current_state = TaskState()\n",
    "\n",
    "for i, user_input in enumerate(conversation_steps):\n",
    "    print(f\"\\n👤 用户: {user_input}\")\n",
    "    \n",
    "    # 添加用户消息\n",
    "    current_state['messages'].append(HumanMessage(content=user_input))\n",
    "    \n",
    "    # 处理任务\n",
    "    result = app.invoke(current_state, config=config)\n",
    "    \n",
    "    # 更新状态\n",
    "    current_state = result\n",
    "    \n",
    "    # 显示AI响应\n",
    "    if current_state['messages'] and isinstance(current_state['messages'][-1], AIMessage):\n",
    "        print(f\"🤖 助手: {current_state['messages'][-1].content}\")\n",
    "    \n",
    "    # 显示当前任务状态\n",
    "    print(f\"📊 当前步骤: {current_state.get('current_step', 0)}\")\n",
    "    print(f\"🏁 任务完成: {current_state.get('task_complete', False)}\")\n",
    "    if current_state.get('task_data'):\n",
    "        print(f\"📋 已收集数据: {current_state['task_data']}\")\n",
    "\n",
    "# 检查保存的记忆\n",
    "print(\"\\n--- 检查保存的任务记忆 ---\")\n",
    "saved_memories = store.search((\"user_456\", \"completed_tasks\"))\n",
    "print(f\"📋 用户完成的任务数量: {len(saved_memories)}\")\n",
    "\n",
    "for memory in saved_memories:\n",
    "    task_data = memory.value\n",
    "    print(f\"✅ {task_data['task_type']}: {task_data['task_data']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "section-5-3",
   "metadata": {},
   "source": [
    "### 5.3.3 TrustCall 信息提取模拟\n",
    "\n",
    "TrustCall 是一个开源库，旨在简化 AI 智能体的基于模式的信息提取和更新。本节演示如何使用结构化方法提取和管理记忆信息。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "example-5-7",
   "metadata": {},
   "source": [
    "##### 示例 5-14：构建一个 TrustCall 可用的数据结构\n",
    "\n",
    "定义用于信息提取的结构化数据模型："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "cell-7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pydantic import BaseModel, Field\n",
    "from typing import List\n",
    "\n",
    "class UserProfile(BaseModel):\n",
    "    \"\"\"具有类型化字段的用户资料模式\"\"\"\n",
    "    user_name: str = Field(description=\"用户的首选姓名\")\n",
    "    interests: List[str] = Field(description=\"用户兴趣列表\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60bed512",
   "metadata": {},
   "source": [
    "##### 示例 5-15：使用 TrustCall 进行基本信息提取"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "df89620c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from trustcall import create_extractor\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "model = ChatOpenAI(model=\"Qwen/Qwen2.5-7B-Instruct\", temperature=0) # 初始化 LLM\n",
    "trustcall_extractor = create_extractor(\n",
    "    model,\n",
    "    tools=[UserProfile], # 将我们的 Pydantic 模式作为工具传递\n",
    "    tool_choice=\"UserProfile\" # 强制 TrustCall 使用 UserProfile 工具进行输出\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "92d6ddad",
   "metadata": {},
   "source": [
    "##### 示例 5-16：访问提取的结构化输出"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "db30309e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "user_name='Alice' interests=['远足', '阅读科幻小说']\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import HumanMessage, AIMessage, SystemMessage\n",
    "\n",
    "conversation = [\n",
    "    HumanMessage(content=\"嗨，我是 Alice。\"),\n",
    "    AIMessage(content=\"很高兴认识你，Alice！\"),\n",
    "    HumanMessage(content=\"我的爱好包括远足和阅读科幻小说。\")\n",
    "]\n",
    "\n",
    "instruction_prompt = \"从以下对话中提取用户资料。\"\n",
    "\n",
    "result = trustcall_extractor.invoke({\"messages\": [SystemMessage(content=instruction_prompt)] + conversation})\n",
    "\n",
    "extracted_profile = result[\"responses\"][0] # 访问提取的 UserProfile 对象\n",
    "print(extracted_profile)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "explanation-5-7",
   "metadata": {},
   "source": [
    "**💡 结构化提取核心价值**：\n",
    "\n",
    "- **模式强制执行**：确保提取的数据严格遵循定义的结构\n",
    "- **智能提取**：利用模式匹配和规则进行复杂的信息提取\n",
    "- **增量更新**：支持基于新信息更新现有数据结构\n",
    "- **可靠性提升**：减少手工解析带来的错误和不一致性"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "section-5-4",
   "metadata": {},
   "source": [
    "## 5.4 LangMem\n",
    "\n",
    "在系统阐述 AI 智能体记忆理论基础，并全面解析 LangGraph 记忆存储架构后，本节将详细介绍如何运用 LangChain 团队最新推出的 LangMem 工具库，将先进的记忆管理功能无缝集成至 LangGraph 智能体。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "172afcee",
   "metadata": {},
   "source": [
    "##### 示例 5-17：使用记忆工具配置 ReAct 智能体"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "a5749b4d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "智能体响应: \n",
      "\n",
      "我已经记住了您的喜好，您喜欢编程。如果之后还有其他需要记录的信息，随时告诉我！\n",
      "记忆检索结果: \n",
      "\n",
      "根据之前的记录，您提到过喜欢编程。需要我帮您做些什么与编程相关的吗？或者您希望更新/删除这个偏好信息？\n"
     ]
    }
   ],
   "source": [
    "from langgraph.prebuilt import create_react_agent\n",
    "from langgraph.store.memory import InMemoryStore\n",
    "from langmem import create_manage_memory_tool, create_search_memory_tool\n",
    "\n",
    "# 创建具有记忆功能的智能体\n",
    "agent = create_react_agent(\n",
    "    \"openai:Qwen/Qwen3-8B\", # 选择您的 LLM\n",
    "    tools=[\n",
    "        # 为智能体配备工具以在“热路径”中管理自己的记忆\n",
    "        create_manage_memory_tool(namespace=(\"memories\",)), # 用于创建、更新、删除记忆的工具\n",
    "        create_search_memory_tool(namespace=(\"memories\",)), # 用于搜索现有记忆的工具\n",
    "    ],\n",
    "    store=InMemoryStore(), # 提供记忆存储以实现持久性\n",
    ")\n",
    "\n",
    "# 执行示例：使用智能体进行简单对话\n",
    "response = agent.invoke({\"messages\": [HumanMessage(content=\"请记住我喜欢编程。\")]})\n",
    "print(\"智能体响应:\", response[\"messages\"][-1].content)\n",
    "\n",
    "# 检索记忆以验证存储\n",
    "search_result = agent.invoke({\"messages\": [HumanMessage(content=\"回忆一下我喜欢什么吗？\")]})\n",
    "print(\"记忆检索结果:\", search_result[\"messages\"][-1].content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e99a0642",
   "metadata": {},
   "source": [
    "##### 示例 5-18：实现提示优化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "c2c1fc1f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "您是一位乐于助人的写作助手。请确保您的回答详细且包含用户可能感兴趣的相关信息。如果可能，提供具体的例子或应用场景来增强回答的质量。\n"
     ]
    }
   ],
   "source": [
    "from langmem import create_prompt_optimizer\n",
    "\n",
    "optimizer = create_prompt_optimizer(\n",
    "    \"openai:Qwen/Qwen2.5-7B-Instruct\", # 选择您的 LLM 以进行优化\n",
    "    kind=\"prompt_memory\", # 选择成功案例优化策略\n",
    "    config={\"max_reflection_steps\": 5, \"min_reflection_steps\": 1}, # 配置优化行为\n",
    ")\n",
    "\n",
    "# 带有反馈的示例对话轨迹\n",
    "trajectories = [\n",
    "    # 没有注释的对话（仅对话内容）\n",
    "    (\n",
    "        [\n",
    "            {\"role\": \"user\", \"content\": \"请告诉我Python的优点\"},\n",
    "            {\"role\": \"assistant\", \"content\": \"Python是一种易于学习和使用的编程语言...\"},\n",
    "            {\"role\": \"user\", \"content\": \"能详细说说它的库支持吗？\"},\n",
    "        ],\n",
    "        None,\n",
    "    ),\n",
    "    # 带有反馈的对话\n",
    "    (\n",
    "        [\n",
    "            {\"role\": \"user\", \"content\": \"Python有哪些流行的库？\"},\n",
    "            {\n",
    "                \"role\": \"assistant\",\n",
    "                \"content\": \"Python有许多流行的库，如NumPy、Pandas、TensorFlow等...\",\n",
    "            },\n",
    "        ],\n",
    "        {\n",
    "            \"score\": 0.8,\n",
    "            \"comment\": \"可以增加库的应用场景和更多细节\",\n",
    "        },\n",
    "    ),\n",
    "    # 注释可以是不同类型的，例如编辑/修订！\n",
    "    (\n",
    "        [\n",
    "            {\"role\": \"user\", \"content\": \"Python和Java相比如何？\"},\n",
    "            {\"role\": \"assistant\", \"content\": \"Python和Java在语法和应用领域上有许多不同...\"},\n",
    "        ],\n",
    "        {\"revised\": \"Python和Java在语法、性能和应用领域上各有优劣...\"},\n",
    "    ),\n",
    "]\n",
    "\n",
    "# 定义写作助手的初始系统提示\n",
    "initial_prompt = \"您是一位乐于助人的写作助手\"\n",
    "\n",
    "# 调用优化器以根据训练数据改进初始提示\n",
    "optimized_prompt = optimizer.invoke(\n",
    "    {\"trajectories\": trajectories, \"prompt\": initial_prompt} # 提供轨迹和要优化的初始提示\n",
    ")\n",
    "\n",
    "print(optimized_prompt) # 输出优化的提示"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "chapter-conclusion",
   "metadata": {},
   "source": "## 📚 本章总结\n\n通过本章的学习，我们深入掌握了 AI 智能体记忆系统的核心理念和实践技术。首先系统阐述了短期记忆与长期记忆的区别与应用，理解了如何通过对话历史管理、截断策略和智能摘要来优化短期记忆，同时学会利用 LangGraph 记忆存储实现跨会话的长期知识积累。接着深入探索了记忆存储的核心操作，包括命名空间设计、语义搜索和向量嵌入技术，使智能体能够基于语义相似性进行智能检索。然后通过个性化推荐、多步骤任务完成等实际应用场景，展示了记忆系统如何显著提升智能体的功能表现，并学习了 TrustCall 等工具进行结构化信息提取。最后介绍了 LangMem 工具库的使用方法，包括记忆管理工具和提示优化功能。这些技术的结合使我们能够构建具有持续学习能力、个性化交互和智能进化特性的下一代 AI 智能体系统。"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}